This document describes how to use streams and sinks to perform simple I/O operations. Streams format the data between client and sink. Sinks are low–level, transferring uninterpreted bytes to/from a file, OpenDoc storage unit, etc. The client of a stream writes, and having writ moves on, neither knowing nor caring if the data went to a file, an OpenDoc storage unit or an in–memory buffer.
In ODF, sinks are SOM objects and streams are C++ objects. Most clients of sinks never see the naked sink; they use smart pointer classes whose constructors instantiate the sink and whose destructors free it. The sink is passed to a stream constructor and the client reads or writes data to/from the stream.
The ODF stream and sink classes are a powerful I/O abstraction. However, the power of the stream/sink paradigm has one drawback: it is not immediately obvious how a client can open a file and read it. The examples below are mostly derived from code shipped with ODF.
How to Write to a File
To write to a file, you need three ingredients: an FW_PFile, an FW_PFileSink, and an FW_CWritableStream. The FW_PFile can be created from an FW_PFileSpecification or from an existing FW_OFileRep. The FW_PFileSink is constructed using the FW_PFile. The FW_CWritableStream is constructed from the FW_PFileSink. The example below is from internal test code.
Reading from a file is similar to writing. An FW_PFile and FW_PFileSink are still required. However, an FW_CReadableStream is used instead of the FW_CWritableStream. The example below was extracted from ODFBitmap (CBitmapContent::InternalizePICTFile in Content.cpp).
You can't. ODF does not currently have an interface for writing resources. Resources are created using ODFRC (or other resouce builder) when the part is built and are then only read by the part.
How to Read From a Resource
Resources come from FW_PResourceFiles. FW_PResourceFiles are constructed from FW_PFileSpecifications. The data within the resource can be read using an FW_CReadableStream or by direct examination of memory. The example below is from ODFButton (CScriptAction::InternalizeScriptFile in Actions.cpp).
Having a resource, the data can be read into a handle with the following call:
FW_CAcquireResourceData aResource(ev, resource);
If the data is an object (as created by ODFRC), the resource can be converted into a sink and passed to a stream as shown below. (The snippet is from PRBitmap.cpp)
FW_PResourceSink sink(ev, aResource);
FW_CReadableStream stream(sink);
Why Use Buffered I/O?
For possible performance improvement. When a stream reads and writes to a sink, it calls the sink's read and write methods. Reading and writing to some sinks (such as an ODStorageUnitSink) is expensive because of the marshalling of arguments. By buffering a sink, these expensive calls are made only when necessary (when the buffer is full or when a seek is done).
How to Do Buffered I/O?
ODF has a class called FW_PBufferedSink. This sink wraps a buffer around an existing sink. The constructor for FW_PBufferedSink takes the ever–present ev, a random access sink (resource and file sinks are random access sinks) and a buffer size. When a stream is constructed from the buffered sink, all data transfers are buffered. As an example, we'll use the file writing example above and wrap a buffer around the file.